// Copyright (c) Microsoft Corporation. 
// Licensed under the MIT license.

#include "StandInsOutliner.h"
#include "SimplygonStyle.h"
#include "SimplygonAssetUserData.h"
#include "ISimplygonPluginUtilitiesModule.h"
#include "StandinActor.h"
#include "Engine/World.h"
#include "ISimplygonPluginUtilitiesModule.h"
#include "SimplygonEditor/Private/Customization/NearPipelineCustomization.h"
#include "SimplygonEditor/Private/Customization/ChartAggregationSettingsCustomizations.h"
#include "SimplygonEditor/Private/Customization/BillboardCloudSettingsCustomization.h"

#include "EditorModeManager.h"
#include "PropertyEditorModule.h"
#include "IDetailsView.h"
#include "Widgets/Views/SListView.h"
#include "Widgets/Layout/SScrollBorder.h"
#include "Widgets/Input/SSearchBox.h"
#include "ScopedTransaction.h"
#include "BSPOps.h"

#include "ITreeItem.h"
#include "StandInMeshActorItem.h"
#include "StaticMeshActorItem.h"
#include "StandInsOutlinerWidgetItem.h"
#include "StandInMeshSelectionActor.h"
#include "TreeItemID.h"
#include "Widgets/Input/SButton.h"
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Engine/StaticMeshActor.h"
#include "Logging/MessageLog.h"
#include "Misc/UObjectToken.h"
#include "SceneOutlinerDragDrop.h"
#include "SlateOptMacros.h"
#include "Misc/ScopedSlowTask.h"
#include "Runtime/Engine/Classes/Engine/Selection.h"
#include "Widgets/Layout/SSpacer.h"
#include "EngineUtils.h"
#include "StandinProxyContainer.h"
#include "StandinUtilitiesModule.h"
#include "IContentBrowserSingleton.h"
#include "ContentBrowserModule.h"

DEFINE_LOG_CATEGORY_STATIC(LogReplacementLODModule, Verbose, All);
#define LOCTEXT_NAMESPACE "StandInsOutliner"

namespace StandInsOutliner
{
	SStandInsOutliner::SStandInsOutliner()
	{
		bNeedsRefresh = true;
		CurrentWorld = nullptr;
		StandinUtilitiesModule = FModuleManager::LoadModulePtr<IStandinUtilitiesModule>("StandinUtilities");
		CurrentStandin = nullptr;
		FCoreUObjectDelegates::OnObjectPropertyChanged.AddRaw(this, &SStandInsOutliner::OnObjectPostEditChange);
	}

	SStandInsOutliner::~SStandInsOutliner()
	{
		DeregisterDelegates();
		DestroySelectionActors();
		CurrentWorld = nullptr;
		
		ReplacementLODTreeRoot.Empty();
		SelectedNodes.Empty();
		AllNodes.Empty();
		SelectionActors.Empty();
		ReplacementLODActorBuildFlags.Empty();
		ReplacementLODActors.Empty();
		PendingActions.Empty();

		FCoreUObjectDelegates::OnObjectPropertyChanged.RemoveAll(this);
	}

	BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
	void SStandInsOutliner::Construct(const FArguments& InArgs)
	{
		CreateSettingsView();
		CreateTreeviewWidget();
		{
			auto Delegate = TreeItemTextFilter::FItemToStringArray::CreateSP(this, &SStandInsOutliner::PopulateSearchStrings);
			SearchBoxFilter = MakeShareable(new TreeItemTextFilter(Delegate));
		}
		

		TSharedRef<SWidget> StandinTreeWidget = 
			SNew(SBorder)
			.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
			//.BorderBackgroundColor(FColor(166, 0, 0))
			.Padding(5.0f)
			[
				SNew(SVerticalBox)
				+ SVerticalBox::Slot()
				.Padding(2.0f)
				.AutoHeight()
				[
					SAssignNew(FilterTextBoxWidget, SSearchBox)
					.HintText(LOCTEXT("FilterSearch", "Search..."))
					.ToolTipText(LOCTEXT("FilterSearchHint", "Type here to search (pressing enter selects the results)"))
					.OnTextChanged(this, &SStandInsOutliner::OnTreeSearchBoxTextChanged)
					.OnTextCommitted(this, &SStandInsOutliner::OnTreeSearchTextCommitted)
				]
				+ SVerticalBox::Slot()
				.Padding(2.0f)
				
				[
					SNew(SScrollBorder, TreeView.ToSharedRef())
					[
						TreeView.ToSharedRef()
					]
				]
				
			];

		/** Main window */
		MainContentPanel = SNew(SVerticalBox);
		ChildSlot
			[
				SNew(SVerticalBox)
				+SVerticalBox::Slot()
				[
					SNew(SBorder)
					.BorderImage(FEditorStyle::GetBrush("ToolPanel.LightGroupBorder"))
					.BorderBackgroundColor(FColor(166, 166, 166))
					[
						MainContentPanel.ToSharedRef()
					]
				]
			];

		/** Buttons (V1) */
		MainContentPanel->AddSlot()
			.AutoHeight()
			.Padding(0.0f, 0.0f, 0.0f, 4.0f)
			[
				SNew(SBorder)
				.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
				//.BorderBackgroundColor(FColor(0, 0, 166))
				.Padding(1.0f)
				[
					CreateButtonWidgets()
				]
				
			];

		/** Outliner and settings (V2) */
		MainContentPanel->AddSlot()
			.FillHeight(1.0f)
			[
				SNew(SSplitter)
				.Orientation(Orient_Horizontal)
				.Style(FEditorStyle::Get(), "ContentBrowser.Splitter")
				+ SSplitter::Slot()
				.Value(0.7)
				[
					StandinTreeWidget
				]
				+ SSplitter::Slot()
				.Value(0.3)
				[
					SNew(SVerticalBox)
					+ SVerticalBox::Slot()
					.AutoHeight()
					.Padding(2.0f)
					[
						SNew(SBorder)
						.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
						[
							SNew(SHorizontalBox)
							+SHorizontalBox::Slot()
							.FillWidth(0.5)
							[
								SNew(SVerticalBox)
								+ SVerticalBox::Slot()
								[
									SNew(STextBlock)
									.Text(LOCTEXT("StandinHeader","StandinActor"))
								]
								+ SVerticalBox::Slot()
								[
									SNew(SHorizontalBox)
									+ SHorizontalBox::Slot()
									.FillWidth(0.5)
									[
										SNew(STextBlock)
										.Text(LOCTEXT("LableEstSizeStandin", "Est. Size:"))
									]
									+ SHorizontalBox::Slot()
									.FillWidth(0.5)
									[
										SNew(STextBlock)
										.Text_Lambda([&]()-> FText 
											{ 
												return CurrentStandin ? FText::AsMemory(uint64(CurrentStandin->GetSelfSize())) : FText::AsMemory(0); 
											})
									]
									
								]
							]
							+ SHorizontalBox::Slot()
							.AutoWidth()
							.FillWidth(0.5)
							[
								SNew(SVerticalBox)
								+ SVerticalBox::Slot()
								[
									SNew(STextBlock)
									.Text(LOCTEXT("SourceActorHeader","SourceActor"))
								]
								+ SVerticalBox::Slot()
								[
									SNew(SHorizontalBox)
									+SHorizontalBox::Slot()
									.FillWidth(0.5)
									[
										SNew(STextBlock)
										.Text(LOCTEXT("SourceActorEstSize", "Est. Size:"))
									]
									+ SHorizontalBox::Slot()
									.FillWidth(0.5)
									[
										SNew(STextBlock)
										.Text_Lambda([&]()-> FText { return CurrentStandin ? FText::AsMemory(uint64(CurrentStandin->GetSourceSize())) : FText::AsMemory(0); })
									]
									
								]
							]
							
						]
					]
					+ SVerticalBox::Slot()
					.Padding(2.0f)
					[
						/** Settings (H2) */
						SNew(SBorder)
						.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
						//.BorderBackgroundColor(FColor(0, 166, 0))
						.Padding(1.0f)
						[
							SettingsView.ToSharedRef()
						]
					]
					
				]
			];

		RegisterDelegates();
	}

	TSharedRef<SWidget> SStandInsOutliner::CreateButtonWidgets()
	{
		FTextBlockStyle ButtonTextStyle = FTextBlockStyle()
			.SetFont(FCoreStyle::GetDefaultFontStyle("Bold", 10))
			.SetColorAndOpacity(FLinearColor(0.0f, 0.0f, 0.0f))
			.SetHighlightColor(FLinearColor(0.0f, 0.0f, 0.0f))
			.SetShadowOffset(FVector2D(0, 0))
			.SetShadowColorAndOpacity(FLinearColor(0.0f, 0.0f, 0.0f, 0.9f));

		return SNew(SVerticalBox)
			+ SVerticalBox::Slot()
			.Padding(FMargin(0.0f, 5.0f))
			[
				SNew(SHorizontalBox)
				+ SHorizontalBox::Slot()
				.AutoWidth()
				.VAlign(VAlign_Center)
				.Padding(FMargin(5.0f, 0.0f))
				[
					SNew(SButton)
					.ButtonStyle(FEditorStyle::Get(), "FlatButton.Light")
					.ContentPadding(FMargin(20.0f, 6.0f))
					.HAlign(HAlign_Center)
					.IsEnabled(this, &SStandInsOutliner::HasStaticMeshActorsInLevel)
					.OnClicked(this, &SStandInsOutliner::CreateStandinClicked)
					.ToolTipText(LOCTEXT("CreateStandinFromSelectionToolTip", "Create a new standin using the actors selected in the level editor"))
					[
						SNew(STextBlock)
						.TextStyle(&ButtonTextStyle)
						.Text(LOCTEXT("CreateStandinFromSelection", "Create"))
					]
				]
				
				+ SHorizontalBox::Slot()
				.AutoWidth()
				.VAlign(VAlign_Center)
				.Padding(FMargin(5.0f, 0.0f))
				[
					SNew(SButton)
					.ButtonStyle(FEditorStyle::Get(), "FlatButton.Light")
					.ContentPadding(FMargin(20.0f, 6.0f))
					.HAlign(HAlign_Center)
					.IsEnabled(this, &SStandInsOutliner::HasStandinActors)
					.OnClicked(this, &SStandInsOutliner::BuildAllClicked)
					.ToolTipText(LOCTEXT("BuildActorsToolTip", "Build all standin actors which are not built yet."))
					[
						SNew(STextBlock)
						.TextStyle(&ButtonTextStyle)
						.Text(LOCTEXT("BuildAll", "Build"))
					]
				]

				+ SHorizontalBox::Slot()
					.AutoWidth()
					.VAlign(VAlign_Center)
					.Padding(FMargin(5.0f, 0.0f))
					[
						SNew(SButton)
						.ButtonStyle(FEditorStyle::Get(), "FlatButton.Light")
					.ContentPadding(FMargin(20.0f, 6.0f))
					.HAlign(HAlign_Center)
					.OnClicked(this, &SStandInsOutliner::RebuildAllClicked)
					.IsEnabled(this, &SStandInsOutliner::HasStandinActors)
					.ToolTipText(LOCTEXT("RebuildActorsToolTip", "Force a build of all standin actors."))
					[
						SNew(STextBlock)
						.TextStyle(&ButtonTextStyle)
						.Text(LOCTEXT("RebuildAll", "Rebuild"))
					]
				]

				
				
			];
	}

	TSharedRef<SWidget> SStandInsOutliner::CreateTreeviewWidget()
	{
		const FSlateFontInfo& HeaderFont = FSimplygonStyle::Get().GetFontStyle("Standin.HeaderFontStyle");
		return SAssignNew(TreeView, SReplacementLODTree)
			.ItemHeight(24.0f)
			.TreeItemsSource(&ReplacementLODTreeRoot)
			.OnGenerateRow(this, &SStandInsOutliner::OnOutlinerGenerateRow)
			.OnGetChildren(this, &SStandInsOutliner::OnOutlinerGetChildren)
			.OnSelectionChanged(this, &SStandInsOutliner::OnOutlinerSelectionChanged)
			.OnMouseButtonDoubleClick(this, &SStandInsOutliner::OnOutlinerDoubleClick)
			.OnContextMenuOpening(this, &SStandInsOutliner::OnOpenContextMenu)
			.OnExpansionChanged(this, &SStandInsOutliner::OnItemExpansionChanged)
			.HeaderRow
			(
				SNew(SHeaderRow)
				/** Editor visibility column */
				+ SHeaderRow::Column("EditorVisibility")
				.HeaderContentPadding(FMargin(0.0f, 10.0f))
				.FixedWidth(24.0f)
				.HeaderContent()
				[
					SNew(STextBlock)
				]

				+ SHeaderRow::Column("SceneActorName")
				//.DefaultLabel(LOCTEXT("SceneActorName", "Name"))
				.VAlignHeader(EVerticalAlignment::VAlign_Center)
				.FillWidth(0.25f)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("SceneActorName", "Name"))
					.Font(HeaderFont)
				]

				+ SHeaderRow::Column("StandinGame")
				.HeaderContentPadding(FMargin(0))
				.FillWidth(0.05f)
				[
					SNew(SButton)
					.ButtonStyle(FEditorStyle::Get(), "ToggleButton")
					.ContentPadding(FMargin(0.0f))
					.HAlign(HAlign_Center)
					.VAlign(VAlign_Center)
					.ToolTipText(LOCTEXT("StandinEditorButtonToolTip", "Enable/Disable all standin actors to be used as game standin.\n\nEnabled: source actors will be removed during cooking and replaced by the standin actor."))
					.OnClicked(this, &SStandInsOutliner::OnUseGameStandinClicked)
					[
						SNew(SImage)
						.Image(FEditorStyle::GetBrush("Level.ScriptIcon16x"))
					]
				]

				+ SHeaderRow::Column("ProcessType")
				.DefaultTooltip(LOCTEXT("ProcessTypeToolTip", "Standin settings type"))
				.HAlignHeader(EHorizontalAlignment::HAlign_Center)
				.VAlignHeader(EVerticalAlignment::VAlign_Center)
				.FillWidth(0.1f)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("ProcessType", "Type"))
					.Font(HeaderFont)
				]

				+ SHeaderRow::Column("SubActors")
				.DefaultLabel(LOCTEXT("SubActorsCount","Source count"))
				.DefaultTooltip(LOCTEXT("SubActorsCountToolTop", "Source Actors (excluding attached actors) / Number of static mesh components (including instances)."))
				.FillWidth(0.15f)
				.HAlignHeader(EHorizontalAlignment::HAlign_Center)
				.VAlignHeader(EVerticalAlignment::VAlign_Center)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("SubActorsCount", "Source count"))
					.Font(HeaderFont)
				]

				+ SHeaderRow::Column("TriangleCount")
				.DefaultTooltip(LOCTEXT("TriangleCountToolTop", "Standin actor triangle count compared to source actors"))
				.FillWidth(0.15f)
				.HAlignHeader(EHorizontalAlignment::HAlign_Center)
				.VAlignHeader(EVerticalAlignment::VAlign_Center)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("TriangleCount", "Triangles"))
					.Font(HeaderFont)
				]

				/*+ SHeaderRow::Column("LODGroup")
				.DefaultLabel(LOCTEXT("LODGroup", "LOD Group"))
				.DefaultTooltip(LOCTEXT("LODGroup", "LOD Group assigned to Standin Mesh"))
				.FillWidth(0.15f)*/

				+ SHeaderRow::Column("Level")
				.DefaultTooltip(LOCTEXT("LevelToolTip", "Level the Standin actor is located."))
				.FillWidth(0.15f)
				.VAlignHeader(EVerticalAlignment::VAlign_Center)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("Level", "Level"))
					.Font(HeaderFont)
				]

				+ SHeaderRow::Column("UserData")
				.DefaultTooltip(LOCTEXT("UserDataToolTip", "User Tags."))
				.FillWidth(0.15f)
				.VAlignHeader(EVerticalAlignment::VAlign_Center)
				[
					SNew(STextBlock)
					.Text(LOCTEXT("UserData", "UserData"))
					.Font(HeaderFont)
				]
			);
	}

	void SStandInsOutliner::CreateSettingsView()
	{
		// Create a property view
		FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked<FPropertyEditorModule>("PropertyEditor");

		FNotifyHook* NotifyHook = this;
		FDetailsViewArgs DetailsViewArgs(
			/*bUpdateFromSelection=*/ false,
			/*bLockable=*/ false,
			/*bAllowSearch=*/ true,
			FDetailsViewArgs::ActorsUseNameArea,
			/*bHideSelectionTip=*/ true,
			/*InNotifyHook=*/ NotifyHook,
			/*InSearchInitialKeyFocus=*/ false,
			/*InViewIdentifier=*/ NAME_None);
		
		DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Automatic;
		DetailsViewArgs.bShowOptions = false;

		SettingsView = EditModule.CreateDetailView(DetailsViewArgs);
		//SettingsView->
		struct Local
		{
			/** Delegate to show all properties */
			static bool IsPropertyVisible(const FPropertyAndParent& PropertyAndParent, bool /*bInShouldShowNonEditable*/)
			{

				const char* CategoryNames[1] =
				{
					"StandinSettings",
				};

				bool bIsSimplygonPipeline = false;
				for (const FProperty* Parent : PropertyAndParent.ParentProperties)
				{
					if (Parent->GetName().Contains(TEXT("Pipeline")))
					{
						bIsSimplygonPipeline = true;
						break;
					}
				}

				FString CategoryName = PropertyAndParent.Property.GetMetaData("Category");
				for (uint32 CategoryIndex = 0; CategoryIndex < 1; ++CategoryIndex)
				{
					if (bIsSimplygonPipeline || CategoryName == CategoryNames[CategoryIndex])
					{
						return true;
					}
				}

				return false;
			}
		};

		SettingsView->SetIsPropertyVisibleDelegate(FIsPropertyVisible::CreateStatic(&Local::IsPropertyVisible, true));
		SettingsView->SetDisableCustomDetailLayouts(false);
		
		// Register customizations	
		FOnGetPropertyTypeCustomizationInstance CustomizeNearPipelineProperty = FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FNearPipelinePropertyCustomization::MakeInstance);
		SettingsView->RegisterInstancedCustomPropertyTypeLayout(FAggregationPipelineSettings::StaticStruct()->GetFName(), CustomizeNearPipelineProperty);

		FOnGetPropertyTypeCustomizationInstance CustomizeChartAggProperty = FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FChartAggregationSettingsPropertyCustomization::MakeInstance);
		SettingsView->RegisterInstancedCustomPropertyTypeLayout(FChartAggregatorSettings::StaticStruct()->GetFName(), CustomizeChartAggProperty);

		FOnGetPropertyTypeCustomizationInstance CustomizeBillboardCloudProperty = FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FBillboardCloudSettingsPropertyCustomization::MakeInstance);
		SettingsView->RegisterInstancedCustomPropertyTypeLayout(FBillboardCloudSettings::StaticStruct()->GetFName(), CustomizeBillboardCloudProperty);
	}

	void SStandInsOutliner::Tick(const FGeometry& /*AllottedGeometry*/, const double /*InCurrentTime*/, const float /*InDeltaTime*/)
	{
		// Get a collection of items and folders which were formerly collapsed
		const FParentsExpansionState ExpansionStateInfo = GetParentsExpansionState();

		if (bNeedsRefresh)
		{
			Populate();
		}

		bool bChangeMade = false;

		// Only deal with 256 at a time
		const int32 End = FMath::Min(PendingActions.Num(), 512);
		for (int32 Index = 0; Index < End; ++Index)
		{
			auto& PendingAction = PendingActions[Index];
			switch (PendingAction.Type)
			{
			case FOutlinerAction::AddItem:
				bChangeMade |= AddItemToTree(PendingAction.Item, PendingAction.ParentItem);
				break;

			case FOutlinerAction::MoveItem:
				MoveItemInTree(PendingAction.Item, PendingAction.ParentItem);
				bChangeMade = true;
				break;

			case FOutlinerAction::RemoveItem:
				RemoveItemFromTree(PendingAction.Item);
				bChangeMade = true;
				break;
			default:
				check(false);
				break;
			}
		}
		PendingActions.RemoveAt(0, End);

		if (bChangeMade)
		{
			// Restore the expansion states
			SetParentsExpansionState(ExpansionStateInfo);

			// Restore expansion states
			TreeView->RequestTreeRefresh();
		}

	}

	void SStandInsOutliner::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
	{
		SCompoundWidget::OnMouseEnter(MyGeometry, MouseEvent);
	}

	void SStandInsOutliner::OnMouseLeave(const FPointerEvent& MouseEvent)
	{
		SCompoundWidget::OnMouseLeave(MouseEvent);
	}

	FReply SStandInsOutliner::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
	{
		return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent);
	}

	FReply SStandInsOutliner::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
	{
		FDragValidationInfo ValidationInfo = FDragValidationInfo::Invalid();
		HandleDrop(DragDropEvent, ValidationInfo, true);
		return SCompoundWidget::OnDrop(MyGeometry, DragDropEvent);
	}

	FReply SStandInsOutliner::OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
	{
		return SCompoundWidget::OnDragOver(MyGeometry, DragDropEvent);
	}
	
	void SStandInsOutliner::OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
	{
		FDragValidationInfo ValidationInfo = FDragValidationInfo::Invalid();
		HandleDrop(DragDropEvent, ValidationInfo, false);
		return SCompoundWidget::OnDragEnter(MyGeometry, DragDropEvent);
	}

	void SStandInsOutliner::PostUndo(bool /*bSuccess*/)
	{
		FullRefresh();
	}

	FReply SStandInsOutliner::BuildAllClicked()
	{
		if (CurrentWorld)
		{
			DestroySelectionActors();
			IStandinUtilitiesModule::Get().BuildStandinMeshActors(false);
		}
		
		return FReply::Handled();
	}

	FReply SStandInsOutliner::RebuildAllClicked()
	{
		if (CurrentWorld)
		{
			DestroySelectionActors();
			IStandinUtilitiesModule::Get().BuildStandinMeshActors(true);
		}

		return FReply::Handled();
	}

	bool SStandInsOutliner::HasStandinActors() const
	{
		TArray<AStandInMeshActor*> Result;
		IStandinUtilitiesModule::Get().GetStandinActors(Result);
		return Result.Num() > 0;
	}

	bool SStandInsOutliner::HasStaticMeshActorsInLevel() const
	{
		UWorld* CurrentActiveWorld = nullptr;
		for (const FWorldContext& Context : GEngine->GetWorldContexts())
		{
			if (Context.WorldType == EWorldType::PIE)
			{
				CurrentActiveWorld = Context.World();
				break;
			}
			else if (Context.WorldType == EWorldType::Editor)
			{
				CurrentActiveWorld = Context.World();
			}
		}

		if (CurrentActiveWorld)
		{
			for (ULevel* Level : CurrentActiveWorld->GetLevels())
			{
				// Only handling visible levels (this is to allow filtering the HLOD outliner per level, should change when adding new sortable-column)
				if (Level->bIsVisible)
				{
					for (AActor* Actor : Level->Actors)
					{
						AStaticMeshActor* StaticMeshActor = Cast<AStaticMeshActor>(Actor);
						if (StaticMeshActor)
						{
							return true;
						}
					}
				}
			}
		}
		return false;
	}

	FReply SStandInsOutliner::CreateStandinClicked()
	{
		USelection* SelectedActors = GEditor->GetSelectedActors();
		TArray<AActor*> SubActors;
		for (FSelectionIterator Iter(*SelectedActors); Iter; ++Iter)
		{
			AActor* Actor = Cast<AActor>(*Iter);
			if (Actor)
			{
				SubActors.Add(Actor);
			}
		}

		IStandinUtilitiesModule::Get().CreateStandinMeshActor(SubActors);
		FullRefresh();

		return FReply::Handled();
	}

	FReply SStandInsOutliner::HandleForceRefresh()
	{
		FullRefresh();

		return FReply::Handled();
	}

	END_SLATE_FUNCTION_BUILD_OPTIMIZATION

	void SStandInsOutliner::RegisterDelegates()
	{
		FEditorDelegates::MapChange.AddSP(this, &SStandInsOutliner::OnMapChange);
		FEditorDelegates::NewCurrentLevel.AddSP(this, &SStandInsOutliner::OnNewCurrentLevel);
		FEditorDelegates::OnMapOpened.AddSP(this, &SStandInsOutliner::OnMapLoaded);
		FEditorDelegates::OnDeleteActorsEnd.AddSP(this, &SStandInsOutliner::OnDeleteActors);
		FWorldDelegates::LevelAddedToWorld.AddSP(this, &SStandInsOutliner::OnLevelAdded);
		FWorldDelegates::LevelRemovedFromWorld.AddSP(this, &SStandInsOutliner::OnLevelRemoved);

		// Selection change
		USelection::SelectionChangedEvent.AddRaw(this, &SStandInsOutliner::OnLevelSelectionChanged);
		USelection::SelectObjectEvent.AddRaw(this, &SStandInsOutliner::OnLevelSelectionChanged);

		GEditor->RegisterForUndo(this);
	}

	void SStandInsOutliner::DeregisterDelegates()
	{
		FEditorDelegates::MapChange.RemoveAll(this);
		FEditorDelegates::NewCurrentLevel.RemoveAll(this);
		FEditorDelegates::OnMapOpened.RemoveAll(this);

		FWorldDelegates::LevelAddedToWorld.RemoveAll(this);
		FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this);

		USelection::SelectionChangedEvent.RemoveAll(this);
		USelection::SelectObjectEvent.RemoveAll(this);

		if (GEditor)
		{
			// Deregister for Undo callbacks
			GEditor->UnregisterForUndo(this);
		}
	}

	void SStandInsOutliner::OnLevelAdded(ULevel* /*InLevel*/, UWorld* /*InWorld*/)
	{
		ResetCachedData();
		FullRefresh();
	}

	void SStandInsOutliner::OnLevelRemoved(ULevel* /*InLevel*/, UWorld* /*InWorld*/)
	{
		ResetCachedData();
		FullRefresh();
	}

	void SStandInsOutliner::OnMapChange(uint32 /*MapFlags*/)
	{
		ResetCachedData();
		FullRefresh();
	}

	void SStandInsOutliner::OnNewCurrentLevel()
	{
		ResetCachedData();
		FullRefresh();
	}

	void SStandInsOutliner::OnMapLoaded(const FString& /*Filename*/, bool /*bAsTemplate*/)
	{
		ResetCachedData();
		FullRefresh();
	}

	void SStandInsOutliner::OnDeleteActors()
	{
		FullRefresh();
	}

	void SStandInsOutliner::BuildStandInMeshActor(bool /*bRebuild*/)
	{
		if (CurrentWorld)
		{
			// This call came from a context menu
			auto SelectedItems = TreeView->GetSelectedItems();

			TArray<AStandInMeshActor*> StandinActorsToProcess;
			// Loop over all selected items (context menu can't be called with multiple items selected that aren't of the same types)
			for (auto SelectedItem : SelectedItems)
			{
				FStandInMeshActorItem* ActorItem = (FStandInMeshActorItem*)(SelectedItem.Get());
				if (ActorItem->StandInMeshActor->HasValidSubActors())
				{
					ActorItem->StandInMeshActor->SetIsDirty(true);
					StandinActorsToProcess.Add(ActorItem->StandInMeshActor.Get());
				}
			}

			IStandinUtilitiesModule::Get().GenerateStandinMeshes(StandinActorsToProcess);

			TreeView->RequestScrollIntoView(SelectedItems[0]);
		}
	}

	void SStandInsOutliner::SelectStandinLODActor()
	{
		if (CurrentWorld)
		{
			// This call came from a context menu
			auto SelectedItems = TreeView->GetSelectedItems();

			// Empty selection and setup for multi-selection
			EmptySelection();
			StartSelection();


			bool bChanged = false;
			// Loop over all selected items (context menu can't be called with multiple items selected that aren't of the same types)
			for (auto SelectedItem : SelectedItems)
			{
				FStandInMeshActorItem* ActorItem = (FStandInMeshActorItem*)(SelectedItem.Get());

				if (ActorItem->StandInMeshActor.IsValid())
				{
					SelectActorInViewport(ActorItem->StandInMeshActor.Get(), 0);
					bChanged = true;
		
				}
			}

			// Done selecting actors
			EndSelection(bChanged);
		}
	}

	void SStandInsOutliner::SelectContainedActors()
	{
		// This call came from a context menu
		auto SelectedItems = TreeView->GetSelectedItems();

		// Empty selection and setup for multi-selection
		EmptySelection();
		StartSelection();

		// Loop over all selected items (context menu can't be called with multiple items selected that aren't of the same types)
		for (auto SelectedItem : SelectedItems)
		{
			FStandInMeshActorItem* ActorItem = (FStandInMeshActorItem*)(SelectedItem.Get());

			AStandInMeshActor* ReplacementActor = ActorItem->StandInMeshActor.Get();

			for(AActor* Actor : ReplacementActor->GetSourceActors())
			{
				SelectActorInViewport(Actor);
			}
		}

		// Done selecting actors
		EndSelection(true);
	}

	void SStandInsOutliner::RemoveFromStandinActor()
	{
		if (CurrentWorld)
		{
			const FScopedTransaction Transaction(LOCTEXT("UndoAction_RemoveStaticMeshActorFromReplacementActor", "Removed Static Mesh Actor From ReplacementActor"));

			// This call came from a context menu
			auto SelectedItems = TreeView->GetSelectedItems();

			// fetch all unique parents from selected items
			TArray<TWeakPtr<ITreeItem>> Parents;
			for (auto SelectedItem : SelectedItems)
			{
				FStaticMeshActorItem* ActorItem = (FStaticMeshActorItem*)(SelectedItem.Get());

				for (auto Parent : ActorItem->GetParents())
				{
					if (!Parent.IsValid())
					{
						UE_LOG(LogReplacementLODModule, Display, TEXT("Parent WeakRef is not valid anymore "));
						continue;
					}

					ITreeItem::TreeItemType Type = Parent.Pin()->GetTreeItemType();
					if (Type == ITreeItem::StandInMeshActor)
					{
						Parents.AddUnique(Parent);
					}
				}
			}

			// for each parent, loop selected items and remove from parent.
			// if parent's childcount is zero, delete parent.
			for (auto Parent : Parents)
			{
				const int TotalChildCount = Parent.Pin()->GetChildren().Num();
				int NumRemovedChildren = 0;

				// loop all selected items
				for (auto SelectedItem : SelectedItems)
				{
					// fetch actor item and actor
					FStaticMeshActorItem* ActorItem = (FStaticMeshActorItem*)(SelectedItem.Get());
					AActor* Actor = Cast<AActor>(ActorItem->StaticMeshActor.Get());

					// try to remove actor
					if (IStandinUtilitiesModule::Get().RemoveActorFromStandinActor(Actor))
					{
						// if removed, post RemoveItem event for the selected item
						PendingActions.Emplace(FOutlinerAction::RemoveItem, SelectedItem);
						NumRemovedChildren++;

						// if the deleted count for this parent matches the total number of children,
						// mark parent for deletion.
						if (NumRemovedChildren == TotalChildCount)
						{
							PendingActions.Emplace(FOutlinerAction::RemoveItem, Parent.Pin());
							FullRefresh();
						}
					}
				}
			}
		}
	}

	void SStandInsOutliner::RemoveStandInMeshActor()
	{
		// This call came from a context menu
		auto SelectedItems = TreeView->GetSelectedItems();

		// Loop over all selected items (context menu can't be called with multiple items selected that aren't of the same types)
		for (auto SelectedItem : SelectedItems)
		{
			FStandInMeshActorItem* ActorItem = (FStandInMeshActorItem*)(SelectedItem.Get());
			AStandInMeshActor* ReplacementActor = ActorItem->StandInMeshActor.Get();
			IStandinUtilitiesModule::Get().DestroyStandinActor(ReplacementActor);
		}

		FullRefresh();
	}

	void SStandInsOutliner::SaveToPackage()
	{
		// This call came from a context menu
		auto SelectedItems = TreeView->GetSelectedItems();

		TMap<AStandInMeshActor*, FString> StandinToPackageName;
		StandinToPackageName.Reserve(SelectedItems.Num());

		// Loop over all selected items (context menu can't be called with multiple items selected that aren't of the same types)
		for (auto SelectedItem : SelectedItems)
		{
			FStandInMeshActorItem* ActorItem = (FStandInMeshActorItem*)(SelectedItem.Get());
			AStandInMeshActor* StandinActor = ActorItem->StandInMeshActor.Get();

			FString DefaultPackageName = FPackageName::GetLongPackagePath(StandinActor->GetOutermost()->GetName());
			DefaultPackageName += FString(TEXT("/Kit_")) + StandinActor->GetName();

			const FString DefaultPath = FPackageName::GetLongPackagePath(DefaultPackageName);
			const FString DefaultName = FPackageName::GetShortName(DefaultPackageName);

			// Initialize SaveAssetDialog config
			FSaveAssetDialogConfig SaveAssetDialogConfig;
			SaveAssetDialogConfig.DialogTitleOverride = LOCTEXT("SaveStandinPackageTitle", "Save standin to standalone package");
			SaveAssetDialogConfig.DefaultPath = DefaultPath;
			SaveAssetDialogConfig.DefaultAssetName = DefaultName;
			SaveAssetDialogConfig.ExistingAssetPolicy = ESaveAssetDialogExistingAssetPolicy::AllowButWarn;
			SaveAssetDialogConfig.AssetClassNames = { UStaticMesh::StaticClass()->GetFName() };

			FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
			FString SaveObjectPath = ContentBrowserModule.Get().CreateModalSaveAssetDialog(SaveAssetDialogConfig);
			if (SaveObjectPath.IsEmpty())
				continue;
			
			const FString PackageName = FPackageName::ObjectPathToPackageName(SaveObjectPath);
			StandinToPackageName.Add(StandinActor, PackageName);
		}

		TArray<AStandInMeshActor*> StandinActors;
		StandinToPackageName.GenerateKeyArray(StandinActors);
		IStandinUtilitiesModule::Get().GenerateStandinMeshes(StandinActors, StandinToPackageName);

		FullRefresh();
	}

	void SStandInsOutliner::AddSimplygonUseAssetData(ESimplygonMetaTagType InTag)
	{
		// This call came from a context menu
		auto SelectedItems = TreeView->GetSelectedItems();

		// Loop over all selected items (context menu can't be called with multiple items selected that aren't of the same types)
		for (auto SelectedItem : SelectedItems)
		{
			FStaticMeshActorItem* ActorItem = (FStaticMeshActorItem*)(SelectedItem.Get());
			AActor* Actor = ActorItem->StaticMeshActor.Get();
			if(Actor)
			{
				TInlineComponentArray<UStaticMeshComponent*> Components;
				Actor->GetComponents(Components);
				for (auto& Component : Components)
				{
					USimplygonAssetUserData* SimplygonUserData = Component->GetAssetUserData<USimplygonAssetUserData>();
					if (!SimplygonUserData)
					{
						SimplygonUserData = NewObject<USimplygonAssetUserData>(Component, USimplygonAssetUserData::StaticClass());
						SimplygonUserData->SetFlags(RF_Transactional);
						SimplygonUserData->MetaTag = InTag;
						Actor->GetRootComponent()->AddAssetUserData(SimplygonUserData);
						Component->AddAssetUserData(SimplygonUserData);
					}
					else
					{
						SimplygonUserData->MetaTag = InTag;
					}
				}
			}
						
		}
	}

	void SStandInsOutliner::RemoveSimplygonUserAssetData()
	{
		// This call came from a context menu
		auto SelectedItems = TreeView->GetSelectedItems();
		bool TagRemoved = false;
		// Loop over all selected items (context menu can't be called with multiple items selected that aren't of the same types)
		for (auto SelectedItem : SelectedItems)
		{
			FStaticMeshActorItem* ActorItem = (FStaticMeshActorItem*)(SelectedItem.Get());
			AActor* Actor = ActorItem->StaticMeshActor.Get();

			if (Actor)
			{
				TInlineComponentArray<UStaticMeshComponent*> Components;
				Actor->GetComponents(Components);
				for (auto& Component : Components)
				{
					if (Component->GetAssetUserData<USimplygonAssetUserData>())
					{
						Component->RemoveUserDataOfClass(USimplygonAssetUserData::StaticClass());
						TagRemoved = true;
					}
					
				}

				if (TagRemoved)
				{
					TArray<FTreeItemPtr> Results;
					TreeItemsMap.MultiFind(ActorItem->GetID(), Results);
					Results.RemoveAll([ActorItem](FTreeItemPtr InItem) 
						{ 
							FStaticMeshActorItem* CurrentItem = (FStaticMeshActorItem*)InItem.Get();
							return CurrentItem->ParentItem == ActorItem->ParentItem;
						});

					for (auto Item : Results)
					{
						FStaticMeshActorItem* CurrentItem = (FStaticMeshActorItem*)Item.Get();
						check(CurrentItem);
						FStandInMeshActorItem* StandInActor = (FStandInMeshActorItem*)CurrentItem->ParentItem.Get();
						check(StandInActor);
						StandInActor->StandInMeshActor->RemoveSubActor(ActorItem->StaticMeshActor.Get());
						if(!StandInActor->StandInMeshActor->HasAnySubActors())
							IStandinUtilitiesModule::Get().DestroyStandinActor(StandInActor->StandInMeshActor.Get());
					}
				}
				
			}
			
		}
		if (TagRemoved)
		{
			FullRefresh();
		}
		
	}


	const bool SStandInsOutliner::UpdateCurrentWorld()
	{
		CurrentWorld = nullptr;
		for (const FWorldContext& Context : GEngine->GetWorldContexts())
		{
			if (Context.WorldType == EWorldType::PIE)
			{
				CurrentWorld = Context.World();
				break;
			}
			else if (Context.WorldType == EWorldType::Editor)
			{
				CurrentWorld = Context.World();
			}
		}

		return (CurrentWorld != nullptr);
	}

	FDragValidationInfo SStandInsOutliner::ValidateDrop(FDragDropPayload& DraggedObjects) const
	{
		if (DraggedObjects.StaticMeshActors.IsSet() && DraggedObjects.StaticMeshActors->Num() > 0)
		{
			UE_LOG(LogReplacementLODModule, Display, TEXT("Outliner ValidateDrop StaticMeshActors"));

			const int32 NumStaticMeshActors = DraggedObjects.StaticMeshActors->Num();

			TArray<AActor*> DraggedActors;

			for (auto Actor : DraggedObjects.StaticMeshActors.GetValue())
			{
				DraggedActors.Add(Actor.Get());
			}

			bool bSameLevelInstance = IStandinUtilitiesModule::Get().AreActorsInSamePersistingLevel(DraggedActors);
			bool bAlreadyBeingReplaced = IStandinUtilitiesModule::Get().AreActorsAlreadyBeingReplaced(DraggedActors);

			if (!bSameLevelInstance)
			{
				return FDragValidationInfo(EStandInsOutlinerActionType::InvalidAction, FStandInsOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("StaticMeshActorsNotInSameLevelAsset", "Static Mesh Actors not in the same level asset (streaming level)"));
			}

			if (bAlreadyBeingReplaced && DraggedObjects.bSceneOutliner)
			{
				return FDragValidationInfo(EStandInsOutlinerActionType::InvalidAction, FStandInsOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("AlreadyClusters", "One or more Static Mesh Actors is already in a cluster"));
			}

			return FDragValidationInfo(EStandInsOutlinerActionType::CreateStandInMeshActor, FStandInsOutlinerDragDropOp::ToolTip_Compatible, LOCTEXT("CreateNewReplacementActor", "Create new Replacement Actor"));
			
		}
		else if (DraggedObjects.StandinLODActors.IsSet() && DraggedObjects.StandinLODActors->Num() > 0)
		{
			UE_LOG(LogReplacementLODModule, Display, TEXT("Outliner ValidateDrop ReplacementActors"));

			const int32 NumOfReplacementActors = DraggedObjects.StandinLODActors->Num();
			auto ReplacementActors = DraggedObjects.StandinLODActors.GetValue();
			TArray<AActor*> DraggedActors;
			for (auto Actor : ReplacementActors)
			{
				DraggedActors.Add(Actor.Get());
			}

			bool bSameLevelInstance = IStandinUtilitiesModule::Get().AreActorsInSamePersistingLevel(DraggedActors);
			
			if (!bSameLevelInstance)
			{
				return FDragValidationInfo(EStandInsOutlinerActionType::InvalidAction, FStandInsOutlinerDragDropOp::ToolTip_MultipleSelection_Incompatible, LOCTEXT("ReplacementActorsNotInSameLevel", "ReplacementLOD Actors not in the same level"));
			}

			if (bSameLevelInstance)
			{
				return FDragValidationInfo(EStandInsOutlinerActionType::MergeStandInMeshActors, FStandInsOutlinerDragDropOp::ToolTip_MultipleSelection_Compatible, LOCTEXT("MergeReplacementActors", "MergeReplacementActors"));
			}

		}
		return FDragValidationInfo(EStandInsOutlinerActionType::InvalidAction, FStandInsOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("No Actors", "No static mesh actors"));
	}

	FReply SStandInsOutliner::HandleDrop(const FDragDropEvent& DragDropEvent, FDragValidationInfo& ValidationInfo, bool bApplyDrop)
	{
		FDragDropPayload DraggedObjects;
		// Validate now to make sure we don't doing anything we shouldn't
		if (!DraggedObjects.ParseDrag(*DragDropEvent.GetOperation()))
		{
			// Invalid selection
			ValidationInfo = FDragValidationInfo(EStandInsOutlinerActionType::InvalidAction, FStandInsOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("InvalidSelection", "Selection contains already clustered Actor(s)"));
			return FReply::Unhandled();
		}

		ValidationInfo = ValidateDrop(DraggedObjects);

		if (!ValidationInfo.IsValid())
		{
			//// Return handled here to stop anything else trying to handle it - the operation is invalid as far as we're concerned
			return FReply::Handled();
		}

		if (bApplyDrop)
		{
			UWorld* OuterWorld = nullptr;
			
			if (DraggedObjects.StaticMeshActors.IsSet() && DraggedObjects.StaticMeshActors->Num() > 0)
			{
				OuterWorld = Cast<UWorld>(DraggedObjects.StaticMeshActors.GetValue()[0]->GetLevel()->GetOuter());
			}

			TArray<AActor*> SubActors;
			
			for (TWeakObjectPtr<AActor> StaticMeshActor : DraggedObjects.StaticMeshActors.GetValue())
			{
				SubActors.Add(StaticMeshActor.Get());
			}

			IStandinUtilitiesModule::Get().CreateStandinMeshActor(SubActors);
			FullRefresh();
		}

		return FReply::Handled();
	}

	TSharedRef<ITableRow> SStandInsOutliner::OnOutlinerGenerateRow(FTreeItemPtr InTreeItem, const TSharedRef<STableViewBase>& OwnerTable)
	{
		TSharedRef<ITableRow> Widget = SNew(SStandInsOutlinerWidgetItem, OwnerTable)
			.TreeItemToVisualize(InTreeItem)
			.Outliner(this)
			.World(CurrentWorld);

		return Widget;
	}

	void SStandInsOutliner::OnOutlinerGetChildren(FTreeItemPtr InParent, TArray<FTreeItemPtr>& OutChildren)
	{
		for (auto& WeakChild : InParent->GetChildren())
		{
			auto Child = WeakChild.Pin();
			// Should never have bogus entries in this list
			check(Child.IsValid());
			OutChildren.Add(Child);
		}
	}

	void SStandInsOutliner::OnOutlinerSelectionChanged(FTreeItemPtr TreeItem, ESelectInfo::Type SelectInfo)
	{
		if (SelectInfo == ESelectInfo::Direct)
		{
			return;
		}

		TArray<FTreeItemPtr> NewSelectedNodes = TreeView->GetSelectedItems();
		TMap<TWeakObjectPtr<UObject>, TWeakObjectPtr<UPackage >> Mapping;
		EmptySelection();
		//TreeView->ClearSelection();

		//// Loop over previously retrieve list of selected nodes
		StartSelection();

		bool bChanged = false;

		for (FTreeItemPtr SelectedItem : NewSelectedNodes)
		{
			if (SelectedItem.IsValid())
			{
				ITreeItem::TreeItemType Type = SelectedItem->GetTreeItemType();
				switch (Type)
				{
					case ITreeItem::StandInMeshActor:
					{
						FStandInMeshActorItem* ActorItem = (FStandInMeshActorItem*)(SelectedItem.Get());
						//NOTE: Uncomment to enabled multi selection and mass setting change for stand in mesh actors. Also need to uncommend commented part in EndSelection
						//SelectedActorObjects.Add(ActorItem->StandInMeshActor);
						SettingsView->SetObject(ActorItem->StandInMeshActor.Get());
						CurrentStandin = ActorItem->StandInMeshActor.Get();
						SelectActorInViewport(ActorItem->StandInMeshActor.Get(), 0);
						bChanged = true;
						break;
					}

					case ITreeItem::StaticMeshActor:
					{
						FStaticMeshActorItem* StaticMeshActorItem = (FStaticMeshActorItem*)(SelectedItem.Get());
						SelectActorInViewport(StaticMeshActorItem->StaticMeshActor.Get(), 0);
						bChanged = true;
						break;
					}
				}
			}
		}
		EndSelection(bChanged);
		SelectedNodes = TreeView->GetSelectedItems();
	}

	void SStandInsOutliner::OnOutlinerDoubleClick(FTreeItemPtr TreeItem)
	{
		ITreeItem::TreeItemType Type = TreeItem->GetTreeItemType();
		const bool bActiveViewportOnly = false;

		switch (Type)
		{

			case ITreeItem::StandInMeshActor:
			{
				FStandInMeshActorItem* ActorItem = (FStandInMeshActorItem*)(TreeItem.Get());
				SelectActorInViewport(ActorItem->StandInMeshActor.Get(), 0);
				GEditor->MoveViewportCamerasToActor(*ActorItem->StandInMeshActor.Get(), bActiveViewportOnly);
				break;
			}
			case ITreeItem::StaticMeshActor:
			{
				FStaticMeshActorItem* StaticMeshActorItem = (FStaticMeshActorItem*)(TreeItem.Get());
				SelectActorInViewport(StaticMeshActorItem->StaticMeshActor.Get(), 0);
				GEditor->MoveViewportCamerasToActor(*StaticMeshActorItem->StaticMeshActor.Get(), bActiveViewportOnly);
				break;
			}
				
		}
	}

	TSharedPtr<SWidget> SStandInsOutliner::OnOpenContextMenu()
	{
		if (!CurrentWorld)
		{
			return nullptr;
		}

		// Build up the menu for a selection
		const bool bCloseAfterSelection = true;
		TSharedPtr<FExtender> Extender = MakeShareable(new FExtender);

		FMenuBuilder MenuBuilder(bCloseAfterSelection, TSharedPtr<FUICommandList>(), Extender);

		// Multi-selection support, check if all selected items are of the same type, if so return the appropriate context menu
		auto SelectedItems = TreeView->GetSelectedItems();
		ITreeItem::TreeItemType Type = ITreeItem::Invalid;
		bool bSameType = true;
		for (int32 SelectedIndex = 0; SelectedIndex < SelectedItems.Num(); ++SelectedIndex)
		{
			if (SelectedIndex == 0)
			{
				Type = SelectedItems[SelectedIndex]->GetTreeItemType();
			}
			else
			{
				// Not all of the same types
				if (SelectedItems[SelectedIndex]->GetTreeItemType() != Type)
				{
					bSameType = false;
					break;
				}
			}
		}

		if (SelectedItems.Num() && bSameType && SelectedItems[0]->bCanGenerateContextMenu)
		{
			SelectedItems[0]->GenerateContextMenu(MenuBuilder, *this);
			return MenuBuilder.MakeWidget();
		}

		return TSharedPtr<SWidget>();
	}

	void SStandInsOutliner::OnItemExpansionChanged(FTreeItemPtr TreeItem, bool bIsExpanded)
	{
		TreeItem->bIsExpanded = bIsExpanded;

		// Expand any children that are also expanded
		for (auto WeakChild : TreeItem->GetChildren())
		{
			auto Child = WeakChild.Pin();
			if (Child->bIsExpanded)
			{
				TreeView->SetItemExpansion(Child, true);
			}
		}
	}

	void SStandInsOutliner::OnLevelSelectionChanged(UObject* Obj)
	{
		USelection* Selection = Cast<USelection>(Obj);
		AActor* SelectedActor = Cast<AStandInMeshActor>(Obj);
		TreeView->ClearSelection();
		DestroySelectionActors();

		if (Selection)
		{
			int32 NumSelected = Selection->Num();
			// TODO changes this for multiple selection support
			for (int32 SelectionIndex = 0; SelectionIndex < NumSelected; ++SelectionIndex)
			{
				AActor* Actor = Cast<AActor>(Selection->GetSelectedObject(SelectionIndex));
				if (Actor)
				{
					auto Item = TreeItemsMap.Find(Actor);
					if (Item)
					{
						SelectItemInTree(*Item);
						TreeView->RequestScrollIntoView(*Item);
					}
				}
			}
		}
		else if (SelectedActor)
		{
			auto Item = TreeItemsMap.Find(SelectedActor);
			if (Item)
			{
				SelectItemInTree(*Item);

				TreeView->RequestScrollIntoView(*Item);
			}
		}
	}

	void SStandInsOutliner::StartSelection()
	{
		GEditor->GetSelectedActors()->BeginBatchSelectOperation();
	}

	void SStandInsOutliner::EmptySelection()
	{
		GEditor->SelectNone(false, true, true);
		SelectedActorObjects.Empty();
		DestroySelectionActors();
	}

	void SStandInsOutliner::DestroySelectionActors()
	{
		if (CurrentWorld)
		{
			for (AStandInMeshSelectionActor* BoundsActor : SelectionActors)
			{
				if (BoundsActor && BoundsActor->IsValidLowLevel())
				{
					CurrentWorld->DestroyActor(BoundsActor);
				}
			}
		}
		SelectionActors.Empty();
	}

	void SStandInsOutliner::SelectActorInViewport(AActor* Actor, const uint32 SelectionDepth /* = 0 */)
	{
		if (Actor == nullptr)
			return;

		GEditor->SelectActor(Actor, true, false);

		if (Actor->IsA<AStandInMeshActor>() && SelectionDepth == 0)
		{
			//CreateBoundingSphereForActor(Actor);
		}
	}

	void SStandInsOutliner::SelectSelfAndSubActorsInViewport(AStandInMeshActor* ReplacementActor, const uint32 SelectionDepth /* = 0 */, bool bSelectSelf /* = false */)
	{
		
		for (AActor* SubActor : ReplacementActor->GetSourceActors())
		{
			SelectActorInViewport(SubActor, SelectionDepth + 1);
		}

		if (bSelectSelf)
			SelectActorInViewport(ReplacementActor, SelectionDepth);
	}

	void SStandInsOutliner::EndSelection(const bool bChange)
	{
		//// Commit selection changes
		GEditor->GetSelectedActors()->EndBatchSelectOperation();

		//NOTE: Uncomment to enabled multi selection and mass setting change for stand in mesh actors. Also need to uncommend commented part in OnOutlinerSelectionChanged
		//SettingsView->SetObjects(SelectedActorObjects, false, true);
		if (bChange)
		{
			// Fire selection changed event
			GEditor->NoteSelectionChange();
		}
	}
	
	void SStandInsOutliner::FullRefresh()
	{
		bNeedsRefresh = true;
	}

	void SStandInsOutliner::Populate()
	{
		ResetCachedData();
		const bool bUpdateWorld = UpdateCurrentWorld();

		checkf(bUpdateWorld == true, TEXT("Could not find UWorld instance in Engine World Context"));
		
		ReplacementLODActorBuildFlags.Empty();
		ReplacementLODActors.Empty();
		AllNodes.Empty();
		for (ULevel* Level : CurrentWorld->GetLevels())
		{
			// Only handling visible levels (this is to allow filtering the HLOD outliner per level, should change when adding new sortable-column)
			if (Level->bIsVisible)
			{
				for (AActor* Actor : Level->Actors)
				{
					if (Actor)
					{
						AStandInMeshActor* StandinActor = Cast<AStandInMeshActor>(Actor);
						
						if (StandinActor)
						{
							StandinActor->OnStanInMeshGenerated().AddRaw(this, &SStandInsOutliner::HandleStandInMeshGenerated);
							FTreeItemRef Item = MakeShareable(new FStandInMeshActorItem(StandinActor));
														
							//add item to tree
							PendingActions.Emplace(FOutlinerAction::AddItem, Item);
							ReplacementLODTreeRoot.Add(Item->AsShared());
														
						
							AllNodes.Add(Item->AsShared());
							ReplacementLODActorBuildFlags.Add(true);
							ReplacementLODActors.AddDefaulted();

							Item->bIsExpanded = true;
							bool bChildPassesFilter = false;

							for (AActor* ChildActor : StandinActor->GetSourceActors())
							{
								FTreeItemRef ChildItem = MakeShareable(new FStaticMeshActorItem(ChildActor,Item));
								AllNodes.Add(ChildItem->AsShared());
								bChildPassesFilter |= SearchBoxFilter->PassesFilter(ChildItem.Get());

								if(bChildPassesFilter)
									PendingActions.Emplace(FOutlinerAction::AddItem, ChildItem, Item);
							}

							//remove item if self and the children do not pass the filter
							if (!bChildPassesFilter && !SearchBoxFilter->PassesFilter(Item.Get()))
							{
								PendingActions.Emplace(FOutlinerAction::RemoveItem, Item);
								ReplacementLODTreeRoot.Remove(Item->AsShared());
							}
							
							ReplacementLODActorBuildFlags.AddZeroed();
							ReplacementLODActors.Add(StandinActor);
							ReplacementLODActorBuildFlags[ReplacementLODActorBuildFlags.Num() - 1] &= StandinActor->IsDirty();

							StandinActor->ForceRecache();
						}
					}
				}
			}
		}

		
		TreeView->RequestTreeRefresh();
		bNeedsRefresh = false;

	}

	void SStandInsOutliner::HandleStandInMeshGenerated()
	{
		bNeedsRefresh = true;
	}

	void SStandInsOutliner::ResetCachedData()
	{
		ReplacementLODTreeRoot.Reset();
		TreeItemsMap.Reset();
		ReplacementLODActorBuildFlags.Reset();
		ReplacementLODActors.Reset();
	}

	TMap<FTreeItemID, bool> SStandInsOutliner::GetParentsExpansionState() const
	{
		FParentsExpansionState States;
		for (const auto& Pair : TreeItemsMap)
		{
			if (Pair.Value->GetChildren().Num())
			{
				States.Add(Pair.Key, Pair.Value->bIsExpanded);
			}
		}

		return States;
	}

	void SStandInsOutliner::SetParentsExpansionState(const FParentsExpansionState& ExpansionStateInfo) const
	{
		for (const auto& Pair : TreeItemsMap)
		{
			auto& Item = Pair.Value;
			if (Item->GetChildren().Num())
			{
				const bool* bIsExpanded = ExpansionStateInfo.Find(Pair.Key);
				if (bIsExpanded)
				{
					TreeView->SetItemExpansion(Item, *bIsExpanded);
				}
				else
				{
					TreeView->SetItemExpansion(Item, Item->bIsExpanded);
				}
			}
		}
	}

	const bool SStandInsOutliner::AddItemToTree(FTreeItemPtr InItem, FTreeItemPtr InParentItem)
	{
	
		const auto ItemID = InItem->GetID();	
		TreeItemsMap.Add(ItemID, InItem);

		if (InParentItem.Get())
		{
			InParentItem->AddChild(InItem->AsShared());
		}

		return true;
	}

	void SStandInsOutliner::MoveItemInTree(FTreeItemPtr InItem, FTreeItemPtr InParentItem)
	{
		if (!InItem->HasSimplygonUserData())
		{
			for (auto CurrentParent : InItem->GetParents())
			{
				if (CurrentParent.IsValid())
				{
					CurrentParent.Pin()->RemoveChild(InItem->AsShared());
				}
			}
		}
		
		if (InParentItem.Get())
		{
			InParentItem->AddChild(InItem->AsShared());
		}
	}

	void SStandInsOutliner::RemoveItemFromTree(FTreeItemPtr InItem)
	{
		TArray<FTreeItemPtr> ExistingItems;
		TreeItemsMap.MultiFind(InItem->GetID(), ExistingItems);
		
		const int32 NumRemoved = TreeItemsMap.RemoveSingle(InItem->GetID(),InItem);
		
		if (!NumRemoved)
		{
			return;
		}

		for (auto Item : ExistingItems)
		{
			for (auto ParentItem : Item->GetParents())
			{
				if (ParentItem.IsValid())
				{
					ParentItem.Pin()->RemoveChild(Item->AsShared());
				}
			}
		}
		
		
	}

	void SStandInsOutliner::SelectItemInTree(FTreeItemPtr InItem)
	{
		DECLARE_DELEGATE_OneParam(SelectItemInTreeDelegate, FTreeItemPtr);
		SelectItemInTreeDelegate SetItemExpansionAll;
		SetItemExpansionAll.BindLambda([&](FTreeItemPtr CurrentItem)
			{
				for (auto Parent : CurrentItem->GetParents())
				{
					if (Parent.IsValid() && !Parent.Pin()->bIsExpanded)
					{
						Parent.Pin()->bIsExpanded = true;
						TreeView->SetItemExpansion(Parent.Pin(), true);

					}
					SetItemExpansionAll.ExecuteIfBound(Parent.Pin());
				}
				
			});
		//SetItemExpansionAll.ExecuteIfBound(InItem);
		TreeView->SetItemSelection(InItem, true);
		TreeView->RequestTreeRefresh();
	}

	FReply SStandInsOutliner::OnUseGameStandinClicked()
	{
		bool bUseGameStandin = false;
		for (AStandInMeshActor* StandinActor : ReplacementLODActors)
		{
			if (StandinActor && !StandinActor->IsGameStandin())
			{
				bUseGameStandin = true;
				break;
			}
		}

		for (AStandInMeshActor* StandinActor : ReplacementLODActors)
		{
			if (StandinActor)
			{
				StandinActor->UseAsGameStandin(bUseGameStandin);
			}
		}
		GEditor->RedrawAllViewports();

		return FReply::Handled();
	}

	FReply SStandInsOutliner::RetrieveActors()
	{
		bNeedsRefresh = true;
		return FReply::Handled();
	}

	void SStandInsOutliner::OnObjectPostEditChange(UObject* /*Object*/, FPropertyChangedEvent& /*PropertyChangedEvent*/)
	{

	}

	void SStandInsOutliner::PopulateSearchStrings(const ITreeItem& Item, TArray< FString >& OutSearchStrings) const
	{
		//PopulateSearchStrings
		OutSearchStrings.Add(Item.GetName());
	}

	void SStandInsOutliner::OnTreeSearchBoxTextChanged(const FText& InFilterText)
	{
		SearchBoxFilter->SetRawFilterText(InFilterText);
		FilterTextBoxWidget->SetError(SearchBoxFilter->GetFilterErrorText());

		
	}

	void SStandInsOutliner::OnTreeSearchTextCommitted(const FText& InFilterText, ETextCommit::Type CommitInfo)
	{
		const FString CurrentFilterText = InFilterText.ToString();
		// We'll only select actors if the user actually pressed the enter key.  We don't want to change
		// selection just because focus was lost from the search text field.
		if (CommitInfo == ETextCommit::OnEnter)
		{
			FullRefresh();
		}
		else if (CommitInfo == ETextCommit::OnCleared)
		{
			OnTreeSearchBoxTextChanged(InFilterText);
			FullRefresh();
		}
	}

};

#undef LOCTEXT_NAMESPACE
